<!DOCTYPE html>
<title>Tests element upgrade order after adding a scoped custom element definition</title>
<meta name="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<link rel="help" href="https://wicg.github.io/webcomponents/proposals/Scoped-Custom-Element-Registries">
<link rel="help" href="https://github.com/WICG/webcomponents/issues/923">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<body>

<script>
let definitionCount = 0;
function nextCustomElementDefinition() {
  const log = [];
  const name = `test-element-${++definitionCount}`;
  const constructor = class extends HTMLElement {
    constructor() {
      super();
      log.push(this.id);
    }
  };
  return {name, constructor, log};
}

function attachShadowForTest(t, {parent, registry}) {
  parent = parent || document.body;
  const host = parent.ownerDocument.createElement('div');
  const shadow = host.attachShadow({mode: 'open', registry});
  parent.appendChild(host);
  t.add_cleanup(() => host.remove());
  return shadow;
}

function createIFrameForTest(t, {parent, nextSibling}) {
  parent = parent || document.body;
  const iframe = parent.ownerDocument.createElement('iframe');
  parent.insertBefore(iframe, nextSibling);
  if (!iframe.contentDocument.body) {
    iframe.contentDocument.body = iframe.contentDocument.createElement('body');
  }
  t.add_cleanup(() => iframe.remove());
  return iframe;
}

test(t => {
  const {name, constructor, log} = nextCustomElementDefinition();

  const registry = new CustomElementRegistry;
  const shadow = attachShadowForTest(t, {registry});
  shadow.innerHTML = `
    <${name} id="a"></${name}>
    <${name} id="b"></${name}>
    <${name} id="c"></${name}>
  `;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'b', 'c']);
}, 'Upgrade in tree order in the same tree scope');

test(t => {
  // document
  // +- shadow1
  // |  +- a
  // |  +- shadow2
  // |     +- b
  // +- shadow3
  //    +- c

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const shadow1 = attachShadowForTest(t, {registry});
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const shadow2 = attachShadowForTest(t, {registry, parent: shadow1});
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  const shadow3 = attachShadowForTest(t, {registry});
  shadow3.innerHTML = `<${name} id="c"></${name}>`;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'b', 'c']);
}, 'Upgrade in shadow-including tree order across tree scopes');

test(t => {
  // document
  // +- shadow1
  // |  +- a
  // |  +- shadow3
  // |     +- c
  // +- shadow2
  //    +- b

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const shadow1 = attachShadowForTest(t, {registry});
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const shadow2 = attachShadowForTest(t, {registry});
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  const shadow3 = attachShadowForTest(t, {registry, parent: shadow1});
  shadow3.innerHTML = `<${name} id="c"></${name}>`;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'c', 'b']);
}, 'Upgrade order does not depend on shadow root attach order');

test(t => {
  // document
  // +- iframe1
  // |   +- shadow1
  // |   |  +- a
  // |   +- iframe2
  // |      +- shadow2
  // |         +- b
  // +- shadow3
  //    +- c

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const iframe1 = createIFrameForTest(t, {});
  const shadow1 = attachShadowForTest(t, {registry, parent: iframe1.contentDocument.body});
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const iframe2 = createIFrameForTest(t, {parent: iframe1.contentDocument.body});
  const shadow2 = attachShadowForTest(t, {registry, parent: iframe2.contentDocument.body});
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  const shadow3 = attachShadowForTest(t, {registry});
  shadow3.innerHTML = `<${name} id="c"></${name}>`;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'b', 'c']);
}, 'Upgrade in association order across documents, then tree order in each document');

test(t => {
  // document
  // +- iframe2
  // |  +- shadow2
  // |     +- b
  // +- iframe1
  // |  +- shadow1
  // |     +- a

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const iframe1 = createIFrameForTest(t, {});
  const shadow1 = attachShadowForTest(t, {registry, parent: iframe1.contentDocument.body});
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const iframe2 = createIFrameForTest(t, {nextSibling: iframe1});
  const shadow2 = attachShadowForTest(t, {registry, parent: iframe2.contentDocument.body});
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'b']);
}, 'Upgrade order is not affected by DOM order between child frames');

test(t => {
  // document
  // +- iframe1
  // |  +- shadow2
  // |     +- b
  // +- iframe2
  // |  +- shadow1
  // |     +- a

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const iframe1 = createIFrameForTest(t, {});
  const iframe2 = createIFrameForTest(t, {});

  const shadow1 = attachShadowForTest(t, {registry});
  const host1 = shadow1.host;
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const shadow2 = attachShadowForTest(t, {registry});
  const host2 = shadow2.host;
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  iframe1.contentDocument.body.appendChild(host2);
  iframe2.contentDocument.body.appendChild(host1);

  registry.define(name, constructor);
  assert_array_equals(log, ['b', 'a']);
}, 'Upgrade order is affected by shadow tree adoption across documents');

test(t => {
  // Create a registry in one window, but associate it with another document first
  // document
  // +- iframe1
  // |   +- shadow1
  // |      +- a
  // +- shadowr2
  //    +- b

  const {name, constructor, log} = nextCustomElementDefinition();
  const registry = new CustomElementRegistry;

  const iframe1 = createIFrameForTest(t, {});
  const shadow1 = attachShadowForTest(t, {registry, parent: iframe1.contentDocument.body});
  shadow1.innerHTML = `<${name} id="a"></${name}>`;

  const shadow2 = attachShadowForTest(t, {registry});
  shadow2.innerHTML = `<${name} id="b"></${name}>`;

  registry.define(name, constructor);
  assert_array_equals(log, ['a', 'b']);
}, 'Elements in the "owner" window of a scoped registry are not always upgraded first');

</script>
</body>
